<?php

namespace model;

/**
 * Klasa bazowa modelu
 *
 * @author Damian Jóźwiak
 */
class ModelBase extends \library\Base {

    /**
     * @readwrite
     */
    protected $table;

    /**
     * zawiera relacje w obie strony na podstawie bazy danych
     * @var Relation[]
     */
    public $relations;

    /**
     * <p>obsługa zapytań do bazy danych</p>
     * @var database\Query
     */
    protected $query = null;

    /**
     * @readwrite
     * @var \model\database\PDOConnector 
     */
    protected $connector;

    /**
     * @read
     */
    protected $types = ["autonumber", "text", "varchar", "int", "decimal", "boolean", "datetime", "enum"];

    /**
     *
     * @var ColumnProperties[]
     */
    protected $columns;

    /**
     * kolumna będąca kluczem głównym
     * @var ColumnProperties 
     */
    protected $primary;

    /**
     * 
     * @return string <p>nazwa tabeli dla danego modelu</p>
     */
    public function getTable() {
        if (empty($this->table)) {
            $class = get_class($this);
            $list = explode("\\", $class);
            $this->table = strtolower($list[sizeof($list) - 1]);
        }
        return $this->table;
    }

    public function getColumnNames($coluns="all") {
        $keys = array_keys($this->getColumns());
        $result = array();
        foreach ($keys as $key) {
            //  $result[]=$key;
            if ($coluns=="all"){
                $result["`" . $this->getTable() . "`.`" . $key . "`"] = "`" . $this->getTable() . "_" . $key . "`";
            }
            elseif(is_array($coluns)){
                if (!in_array($key, $coluns)){
                    continue;
                }
                $result["`" . $this->getTable() . "`.`" . $key . "`"] = "`" . $this->getTable() . "_" . $key . "`";
            }
            
        }
        return $result;
    }

    public function getRelation($table1, $table2) {
        $this->getColumns();
        foreach ($this->relations as $relation) {
            if (strcmp($relation->tableName, $table1) == 0 && strcmp($relation->referencedTableName, $table2) == 0) {
                return $relation->getRelationAsString();
            }
            if (strcmp($relation->tableName, $table2) == 0 && strcmp($relation->referencedTableName, $table1) == 0) {
                return $relation->getRelationAsString();
            }
        }
        return false;
    }

    /**
     * 
     * @return \model\database\PDOConnector <p>obiekt na ktorym mozna wywolywac zapytania z bazy danych</p>
     * @throws \library\exceptions\model\ExceptionConnectorNotFound
     */
    public function getConnector() {
        if (empty($this->connector)) {
            $database = \library\Registry::get("database");
            if (!$database) {
                throw new exceptions\ExceptionConnectorNotFound();
            }
            $this->connector = $database->initialize();
        }
        $this->connector->connect();
        return $this->connector;
    }

    /**
     * zwraca listę kolumn dla tabeli odpowiedniej dla danego modelu
     * @return ColumnProperties[] <p>asocjacyjna tablica, kluczami są nazwy kolumn</p>
     * @throws \model\exceptions\ExceptionWrongType
     * @throws \model\exceptions\ExceptionPrimaryColumn
     */
    public function getColumns() {
        if (empty($this->columns)) {
            $primaries = 0;
            $columns = array();
            $class = get_class($this);
            $types = $this->types;
            $inspector = new \library\Inspector($class);
            $properties = $inspector->getClassProperties();
            $first = function($array, $key) {
                if (!empty($array[$key]) && sizeof($array[$key]) == 1) {
                    return $array[$key][0];
                }
                return null;
            };
            foreach ($properties as $property) {
                $columnProperties = new ColumnProperties();
                $propertyMeta = $inspector->getPropertyMeta($property);
                if (!empty($propertyMeta["@column"])) {
                    $columnProperties->raw = $property;
                    $columnProperties->name = preg_replace("#^_#", "", $property);
                    $columnProperties->primary = !empty($propertyMeta["@primary"]);
                    $columnProperties->type = $first($propertyMeta, "@type");
                    $columnProperties->length = $first($propertyMeta, "@length");
                    $columnProperties->index = !empty($propertyMeta["@index"]);
                    $columnProperties->readwrite = !empty($propertyMeta["@readwrite"]);
                    $columnProperties->read = !empty($propertyMeta["@read"]) || !empty($propertyMeta["@readwrite"]);
                    $columnProperties->write = !empty($propertyMeta["@write"]) || !empty($propertyMeta["@readwrite"]);
                    $columnProperties->validate = !empty($propertyMeta["@validate"]) ? $propertyMeta["@validate"] : null;
                    $columnProperties->label = $first($propertyMeta, "@label");
                    $columnProperties->isObject = !empty($propertyMeta["@isObject"]);
                    if ($columnProperties->type == null) {
                        throw new \model\exceptions\ExceptionWrongType("pole " . $columnProperties->name . " klasy " . $this->getTable() . " nie ma określonego typu");
                    }
                    if (!in_array($columnProperties->type, $types) && !$columnProperties->isObject) {
                        throw new \model\exceptions\ExceptionWrongType($columnProperties->type);
                    }
                    if ($columnProperties->primary) {
                        $this->primary = $columnProperties;
                        $primaries++;
                    }
                    $columns[ucfirst($columnProperties->name)] = $columnProperties;
                }
            }            
            $this->_getColumnsFromDatabase($columns, $primaries);
            if ($primaries !== 1) {
                throw new \model\exceptions\ExceptionPrimaryColumn("klasa {$class} musi mieć dokładnie jedną kolumną główną");
            }
            $this->columns = $columns;
        }
            
        return $this->columns;
    }

    /**
     * <p> w przypadku gdy model nie zawiera definicji tabeli ta jest brana z bazy danych</p>
     * @param ColumnProperties[] $columns <p>referencja do listy kolumn obiektu</p>
     * @param int $primaries <p>referencja do ilości kluczy głównych w obiekcie</p>
     * @throws exceptions\TableNotExistsException
     */
    protected function _getColumnsFromDatabase(&$columns, &$primaries) {
        $this->getConnector();
        $this->connector->connect();
        $kolumny = $this->connector->executeSQL("select * from INFORMATION_SCHEMA.COLUMNS where TABLE_NAME='" . $this->getTable() . "'")->fetchAll(database\PDOConnector::FETCH_ASSOC);
        if (!is_array($kolumny) || sizeof($kolumny) == 0) {
            throw new exceptions\TableNotExistsException($this->getTable());
        }
        $klucze_obce = $this->connector->executeSQL("SELECT i.TABLE_NAME, i.CONSTRAINT_TYPE, i.CONSTRAINT_NAME, k.REFERENCED_TABLE_NAME, k.REFERENCED_COLUMN_NAME, k.COLUMN_NAME FROM information_schema.TABLE_CONSTRAINTS i LEFT JOIN information_schema.KEY_COLUMN_USAGE k ON i.CONSTRAINT_NAME = k.CONSTRAINT_NAME WHERE i.CONSTRAINT_TYPE = 'FOREIGN KEY' AND i.TABLE_NAME = '" . $this->getTable() . "' OR k.REFERENCED_TABLE_NAME='" . $this->getTable() . "'")->fetchAll();
        $this->connector->disconnect();
        $inspector = $this->inspector;
        foreach ($klucze_obce as $value) {
            $relation = new Relation();
            $relation->tableName = "`" . $value["TABLE_NAME"] . "`";
            $relation->referencedTableName = "`" . $value["REFERENCED_TABLE_NAME"] . "`";
            $relation->columnName = "`" . $value['COLUMN_NAME'] . "`";
            $relation->referencedColumnName = "`" . $value["REFERENCED_COLUMN_NAME"] . "`";
            $relation->constraint = $value["CONSTRAINT_NAME"];
            $this->relations[] = $relation;
        }
        //var_dump($this->relations);
        foreach ($kolumny as $pole) {
            $columnProperties = new ColumnProperties();
            $columnProperties->name = $pole['COLUMN_NAME'];
            if (!property_exists(get_class(), $columnProperties->name) && !$inspector->propertyExists($columnProperties->name)) {
                throw new exceptions\PropertyNotExistsException("Pole " . $columnProperties->name . " istnieje w bazie danych ale nie istnieje w klasie " . $this->getTable());
            }
            if (isset($columns[$columnProperties->name])) {
                continue;
            }
            $propertyMeta = $inspector->getPropertyMeta($columnProperties->name);
            $columnProperties->raw = $pole['COLUMN_NAME'];
            if (strcmp($pole['COLUMN_KEY'], "PRI") == 0) {
                $primaries++;
                $columnProperties->primary = true;
            }
            $columnProperties->readwrite = !empty($propertyMeta["@readwrite"]);
            $columnProperties->read = !empty($propertyMeta["@read"]) || !empty($propertyMeta["@readwrite"]);
            $columnProperties->write = !empty($propertyMeta["@write"]) || !empty($propertyMeta["@readwrite"]);
            $columnProperties->type = $pole['DATA_TYPE'];
            if ($pole['NUMERIC_PRECISION'] != null) {
                $columnProperties->length = $pole['NUMERIC_PRECISION'];
            }
            if ($pole['CHARACTER_MAXIMUM_LENGTH'] != null) {
                $columnProperties->length = $pole['CHARACTER_MAXIMUM_LENGTH'];
            }
            if (strcmp($pole['COLUMN_KEY'], "MUL") == 0) {
                $columnProperties->isObject = true;
                foreach ($klucze_obce as $value) {
                    if ($value['COLUMN_NAME'] == $pole['COLUMN_NAME']) {
                        $columnProperties->type = $value['REFERENCED_TABLE_NAME']; //nazwa zaczyna sie od Tbl   
                    }
                }
            }
            $columns[ucfirst($columnProperties->name)] = $columnProperties;
        }
        $this->connector = null;
    }

    /**
     * 
     * @param string $name nazwa kolumny
     * @return ColumnProperties
     */
    public function getColumn($name) {
        $this->getColumns();
        return \library\ArrayMethods::getElement($name, $this->columns);
    }

    /**
     * pobiera klucz główny
     * @return ColumnProperties
     */
    public function getPrimaryColumn() {
        $this->getColumns();
        if (!isset($this->primary)) {
            foreach ($this->columns as $column) {
                if ($column->primary) {
                    $this->primary = $column;
                    break;
                }
            }
        }
        return $this->primary;
    }

    public function __construct($options = array()) {
        parent::__construct($options);
        //$this->getConnector();
    }

    public function save() {
        $this->getConnector();
        $this->getColumns();
        $primary = $this->primary;
        $class = get_class($this);
        if (!$primary){
             throw new \model\exceptions\ExceptionPrimaryColumn("klasa {$class} musi mieć dokładnie jedną kolumną główną");
        }
        $raw = $primary->raw;
        ////var_dump($this->primary);
       // die();
        $name = $primary->name;
        $this->getConnector();
        $query = $this->connector->query()->from($this->getTable());
        if (!empty($this->$raw)) {
            $query->where(" {$name} = ?", $this->$raw);
        }
        $data = array();
       foreach ($this->columns as $key => $column) {
            if ($column->read && !$column->isObject) {
                $prop = $column->raw;
                $data[$key] = $this->$prop;
                continue;
            }
            if ($column != $this->primary && $column) {
                $method = "get" . ucfirst($key);
                $data[$key] = $this->$method();
                $nameCol = $column->name;
                // echo $nameCol."<br>";
                if (is_object($this->$nameCol) && $this->$nameCol instanceof \model\ModelBase) {
                    $this->$nameCol->save();
                    $data[$key] = $this->$nameCol->getPrimaryKey();
                    continue;
                }
                if ($column->type == "datetime") {
                    $time = new \DateTime();
                    $data[$key] = $time->format('Y-m-d H:i:s');
                    continue;
                    //continue;//data to zawsze bieżący timestamp
                }
                if ($column->isObject && $column->type != "datetime") { 
                  //  echo $column->name."<br>";
                    $prop = $column->raw;
                   // var_dump($this->$prop);
                    if (is_object($this->$prop)){
                      //  var_dump($this->$prop);
                        $this->$prop->save();
                        $data[$key] = $key;
                    }
                    else {
                       $data[$key] = $this->$prop;
                    }
                    
                }
            }
        }
        $result = $query->save($data);
        if ($result > 0) {
            $this->$raw = $result;
        }
        $this->connector = null;
        return $result;
    }

    public function delete() {
        $primary = $this->primary;
        $raw = $primary->raw;
        $name = $primary->name;
        if (!empty($this->$raw)) {
            return $this->connector->query()->from($this->getTable())->where(" {$name} = ?", $this->$raw)->delete();
        }
    }

    public static function deleteAll(Criteria $criteria) {
        return $criteria->delete();
    }

    public function setReferences(&$objects, array $fields = []) {
      // var_dump($objects);
        $columns = $this->getColumns();
        //var_dump($objects);
        foreach ($columns as $key => $column) {
            if (sizeof($fields) > 0 && !in_array($column->name, $fields)) {
                continue;
            }
            if ($column->isObject) {
                $type = $column->type;
                $set = "set" . ucfirst($key);
                if (isset($objects[$type])) {
                   
                    $this->$set($objects[$type]);
                    unset($objects[$type]);
                    //echo "dla ".get_class()." istnieje ".$type."<br>";
                }
            }
        }
    }

    public function getPrimaryKey() {
        $col = $this->getPrimaryColumn()->name;
        return $this->$col;
    }

    /**
     * <p>pobiera pierwszy rekord</p>
     * @param \model\Criteria $criteria
     * @param type $page
     * @return type
     */
    public function first(Criteria $criteria, $page = 1) {

        $this->getConnector();
        $criteria->limit(1, $page);
        $all = $criteria->all();
        if (sizeof($all) != 0) {
            $row = $criteria->all()[0];
            $objects = array();
            foreach ($criteria->joinedTables as $table) {
                $class = "\model\\" . ucfirst($table);
                $objects[$table] = new $class($row[$table]);
            }
            foreach ($objects as $obi) {
                $obi->setReferences($objects);
            }
            return \library\ArrayMethods::first($objects);
        }
        return null;
    }

    public function all(Criteria $criteria, $page = 1) {
        $this->getConnector();
        $all = $criteria->all();
        $objects = array();
        foreach ($all as $row) {
            $temp = array();
            foreach ($criteria->joinedTables as $table) {
                $class = "\model\\" . ucfirst($table);
                $temp[$table] = new $class($row[$table]);
            }
          //  var_dump($temp);
            foreach ($temp as $obi) {
                $obi->setReferences($temp);
              //  echo "<hr>";
            }
            $objects[] = \library\ArrayMethods::first($temp);
        }
        return $objects;
    }

    public function count(Criteria $criteria) {
        return $criteria->count();
    }

}
